using System;
using System.Threading;
using System.Collections.Generic;

namespace GameFrame
{
    public static class ComponentTypeId
    {
        private static bool _initAcquired;
        private static bool _initFinished;
        private static List<System.Type> _typeList;
        private static List<int> _sizeList;
        private static List<ComponentCallbacks> _callbacksList;
        private static List<ComponentFlags> _flagsList;
        private static Dictionary<System.Type, int> _reverseLookup;
        private static Dictionary<string, int> _reverseStringLookup;
        public static System.Type[] Type;
        public static int[] Size;
        public static ComponentFlags[] Flags;
        public static ComponentCallbacks[] Callbacks;
        public const int COMPONENT_START_INDEX = 1;

        public static int GetComponentIndex(System.Type type)
        {
            int componentIndex;
            if (ComponentTypeId._reverseLookup.TryGetValue(type, out componentIndex))
                return componentIndex;
            throw new InvalidOperationException("Type not registered: " + type.FullName);
        }

        internal static int GetComponentIndex(string shortTypeName)
        {
            int componentIndex;
            if (ComponentTypeId._reverseStringLookup.TryGetValue(shortTypeName, out componentIndex))
                return componentIndex;
            throw new InvalidOperationException("Type not registered: " + shortTypeName);
        }

        public static int GetComponentSize(int index) => ComponentTypeId.Size[index];

        public static System.Type GetComponentType(int index) => ComponentTypeId.Type[index];

        public static void Setup(Action action)
        {
            if (Volatile.Read(ref ComponentTypeId._initAcquired))
            {
                do
                    ;
                while (!Volatile.Read(ref ComponentTypeId._initFinished));
            }
            else
            {
                lock (typeof (ComponentTypeId.Lock))
                {
                    if (Volatile.Read(ref ComponentTypeId._initAcquired))
                    {
                        do
                            ;
                        while (!Volatile.Read(ref ComponentTypeId._initFinished));
                    }
                    else
                    {
                        Volatile.Write(ref ComponentTypeId._initAcquired, true);
                        ComponentTypeId._typeList = new List<System.Type>();
                        ComponentTypeId._typeList.Add((System.Type) null);
                        ComponentTypeId._sizeList = new List<int>();
                        ComponentTypeId._sizeList.Add(-1);
                        ComponentTypeId._flagsList = new List<ComponentFlags>();
                        ComponentTypeId._flagsList.Add(ComponentFlags.None);
                        ComponentTypeId._callbacksList = new List<ComponentCallbacks>();
                        ComponentTypeId._callbacksList.Add(new ComponentCallbacks()
                        {
                            Serialize = (ComponentSerializeDelegate) ((a, b) =>
                            {
                                throw new NotImplementedException();
                            })
                        });
                        ComponentTypeId._reverseLookup = new Dictionary<System.Type, int>();
                        ComponentTypeId._reverseStringLookup = new Dictionary<string, int>();
                        ComponentTypeId.Add<Transform2D>(new ComponentCallbacks(new ComponentSerializeDelegate(Transform2D.Serialize)));
                        ComponentTypeId.Add<Transform3D>(new ComponentCallbacks(new ComponentSerializeDelegate(Transform3D.Serialize)));
                        ComponentTypeId.Add<Transform2DVertical>(new ComponentCallbacks(new ComponentSerializeDelegate(Transform2DVertical.Serialize)));
                        ComponentTypeId.Add<PhysicsCollider2D>(new ComponentCallbacks(new ComponentSerializeDelegate(PhysicsCollider2D.Serialize), new ComponentChangedDelegate(PhysicsCollider2D.OnAdded), new ComponentChangedDelegate(PhysicsCollider2D.OnRemoved)));
                        ComponentTypeId.Add<PhysicsBody2D>(new ComponentCallbacks(new ComponentSerializeDelegate(PhysicsBody2D.Serialize), new ComponentChangedDelegate(PhysicsBody2D.OnAdded), new ComponentChangedDelegate(PhysicsBody2D.OnRemoved)));
                        ComponentTypeId.Add<PhysicsCollider3D>(new ComponentCallbacks(new ComponentSerializeDelegate(PhysicsCollider3D.Serialize), new ComponentChangedDelegate(PhysicsCollider3D.OnAdded), new ComponentChangedDelegate(PhysicsCollider3D.OnRemoved)));
                        ComponentTypeId.Add<PhysicsBody3D>(new ComponentCallbacks(new ComponentSerializeDelegate(PhysicsBody3D.Serialize), new ComponentChangedDelegate(PhysicsBody3D.OnAdded), new ComponentChangedDelegate(PhysicsBody3D.OnRemoved)));
                        ComponentTypeId.Add<PhysicsJoints2D>(new ComponentCallbacks(new ComponentSerializeDelegate(PhysicsJoints2D.Serialize), new ComponentChangedDelegate(PhysicsJoints2D.OnAdded), new ComponentChangedDelegate(PhysicsJoints2D.OnRemoved)));
                        ComponentTypeId.Add<PhysicsJoints3D>(new ComponentCallbacks(new ComponentSerializeDelegate(PhysicsJoints3D.Serialize), new ComponentChangedDelegate(PhysicsJoints3D.OnAdded), new ComponentChangedDelegate(PhysicsJoints3D.OnRemoved)));
                        ComponentTypeId.Add<PhysicsCallbacks2D>(new ComponentCallbacks(new ComponentSerializeDelegate(PhysicsCallbacks2D.Serialize), new ComponentChangedDelegate(PhysicsCallbacks2D.OnAdded), new ComponentChangedDelegate(PhysicsCallbacks2D.OnRemoved)));
                        ComponentTypeId.Add<PhysicsCallbacks3D>(new ComponentCallbacks(new ComponentSerializeDelegate(PhysicsCallbacks3D.Serialize), new ComponentChangedDelegate(PhysicsCallbacks3D.OnAdded), new ComponentChangedDelegate(PhysicsCallbacks3D.OnRemoved)));
                        ComponentTypeId.Add<CharacterController2D>(new ComponentCallbacks(new ComponentSerializeDelegate(CharacterController2D.Serialize)));
                        ComponentTypeId.Add<CharacterController3D>(new ComponentCallbacks(new ComponentSerializeDelegate(CharacterController3D.Serialize)));
                        ComponentTypeId.Add<NavMeshPathfinder>(new ComponentCallbacks(new ComponentSerializeDelegate(NavMeshPathfinder.Serialize), new ComponentChangedDelegate(NavMeshPathfinder.OnAdded), new ComponentChangedDelegate(NavMeshPathfinder.OnRemoved)));
                        ComponentTypeId.Add<NavMeshSteeringAgent>(new ComponentCallbacks(new ComponentSerializeDelegate(NavMeshSteeringAgent.Serialize), new ComponentChangedDelegate(NavMeshSteeringAgent.OnAdded), new ComponentChangedDelegate(NavMeshSteeringAgent.OnRemoved)));
                        ComponentTypeId.Add<NavMeshAvoidanceAgent>(new ComponentCallbacks(new ComponentSerializeDelegate(NavMeshAvoidanceAgent.Serialize), new ComponentChangedDelegate(NavMeshAvoidanceAgent.OnAdded), new ComponentChangedDelegate(NavMeshAvoidanceAgent.OnRemoved)));
                        ComponentTypeId.Add<NavMeshAvoidanceObstacle>(new ComponentCallbacks(new ComponentSerializeDelegate(NavMeshAvoidanceObstacle.Serialize), new ComponentChangedDelegate(NavMeshAvoidanceObstacle.OnAdded), new ComponentChangedDelegate(NavMeshAvoidanceObstacle.OnRemoved)));
                        ComponentTypeId.Add<View>(new ComponentCallbacks(new ComponentSerializeDelegate(View.Serialize)));
                        ComponentTypeId.Add<MapEntityLink>(new ComponentCallbacks(new ComponentSerializeDelegate(MapEntityLink.Serialize)));
                        action();
                        ComponentTypeId.Type = ComponentTypeId._typeList.ToArray();
                        ComponentTypeId.Size = ComponentTypeId._sizeList.ToArray();
                        ComponentTypeId.Flags = ComponentTypeId._flagsList.ToArray();
                        ComponentTypeId.Callbacks = ComponentTypeId._callbacksList.ToArray();
                        ComponentTypeId._typeList = (List<System.Type>) null;
                        ComponentTypeId._sizeList = (List<int>) null;
                        ComponentTypeId._callbacksList = (List<ComponentCallbacks>) null;
                        Volatile.Write(ref ComponentTypeId._initFinished, true);
                    }
                }
            }
        }

        public static void Add<T>(
            ComponentSerializeDelegate serialize,
            ComponentChangedDelegate onAdded,
            ComponentChangedDelegate onRemoved,
            ComponentFlags flags = ComponentFlags.None)
            where T : unmanaged, IComponent
        {
            ComponentTypeId.Add<T>(new ComponentCallbacks(serialize, onAdded, onRemoved), flags);
        }

        public static void Add<T>(ComponentCallbacks callbacks, ComponentFlags flags = ComponentFlags.None) where T : unmanaged, IComponent
        {
            ComponentTypeId<T>.Id = ComponentTypeId._typeList.Count;
            ComponentTypeId<T>.Size = sizeof (T);
            ComponentTypeId<T>.Flags = flags;
            ComponentTypeId<T>.Callbacks = callbacks;
            ComponentTypeId._reverseLookup.Add(typeof (T), ComponentTypeId._typeList.Count);
            ComponentTypeId._reverseStringLookup.Add(typeof (T).Name, ComponentTypeId._typeList.Count);
            ComponentTypeId._typeList.Add(typeof (T));
            ComponentTypeId._sizeList.Add(ComponentTypeId<T>.Size);
            ComponentTypeId._callbacksList.Add(callbacks);
            ComponentTypeId._flagsList.Add(flags);
        }

        private class Lock
        {
        }
    }

    public static class ComponentTypeId<T> where T : unmanaged, IComponent
    {
        private static int _id;
        private static int _size;
        private static ComponentCallbacks _callbacks;
        private static ComponentFlags _flags;

        public static int Id
        {
            get
            {
                Assert.Check(ComponentTypeId<T>._id > 0);
                return ComponentTypeId<T>._id;
            }
            set
            {
                Assert.Check(ComponentTypeId<T>._id == 0);
                ComponentTypeId<T>._id = value;
            }
        }

        public static int Size
        {
            get
            {
                Assert.Check(ComponentTypeId<T>._size > 0);
                return ComponentTypeId<T>._size;
            }
            set
            {
                Assert.Check(ComponentTypeId<T>._size == 0);
                ComponentTypeId<T>._size = value;
            }
        }

        public static ComponentFlags Flags
        {
            get => ComponentTypeId<T>._flags;
            set
            {
                Assert.Check(ComponentTypeId<T>._flags == ComponentFlags.None);
                ComponentTypeId<T>._flags = value;
            }
        }

        public static ComponentCallbacks Callbacks
        {
            get
            {
                Assert.Check(ComponentTypeId<T>._callbacks.Serialize != null);
                return ComponentTypeId<T>._callbacks;
            }
            set
            {
                Assert.Check(ComponentTypeId<T>._callbacks.Serialize == null);
                ComponentTypeId<T>._callbacks = value;
            }
        }
    }
}